package com.smile.frame.api.es.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.smile.frame.config.es.constants.EsIndexNameConstant;
import com.smile.frame.api.es.document.Product;
import com.smile.frame.api.es.dto.QueryProductDTO;
import com.smile.frame.api.es.enums.EsProductErrorCode;
import com.smile.frame.api.es.enums.ProductPriceSortEnum;
import com.smile.frame.api.es.service.IEsProductService;
import com.smile.frame.config.es.service.IEsService;
import com.smile.frame.config.es.utils.EsBaseUtil;
import com.smile.frame.common.exception.BizException;
import com.smile.frame.common.page.PageResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.StringUtils;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.ScoreSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.xcontent.XContentType;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * @author LGC
 * @date 2021/12/3 10:05
 */
@Slf4j
@Service
public class EsProductServiceImpl implements IEsProductService {
    private final static String INDEX_NAME = EsIndexNameConstant.PRODUCT_INDEX;

    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;
    @Autowired
    private RestHighLevelClient restHighLevelClient;
    @Autowired
    private IEsService esService;

    @Override
    public Boolean insertProduct(Product product) {
        log.info("插入产品文档 doc:{}", product);
        try {
            if (!esService.isIndexExist(INDEX_NAME)) {
                esService.createIndex(INDEX_NAME);
            }
            BulkRequest bulkRequest = new BulkRequest();
            // code做为索引的id
            IndexRequest indexRequest = new IndexRequest(INDEX_NAME)
                    .id(String.valueOf(product.getCode()))
                    .source(JSONUtil.toJsonStr(product), XContentType.JSON);
            bulkRequest.add(indexRequest);

            BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
            if (bulkResponse.hasFailures()) {
                throw new BizException(EsProductErrorCode.PRODUCT_DOCUMENT_BATCH_INSERT_ERROR);
            }
        } catch (IOException e) {
            log.error("插入产品文档异常", e);
            return false;
        }
        return true;
    }

    @Override
    public Boolean batchInsertProduct(List<Product> products) {
        log.info("批量插入产品文档 doc：{}", products);
        try {
            if (!esService.isIndexExist(INDEX_NAME)) {
                esService.createIndex(INDEX_NAME);
            }
            BulkRequest bulkRequest = new BulkRequest();
            IndexRequest indexRequest = null;
            for (Product product : products) {
                // code做为索引的id
                indexRequest = new IndexRequest(INDEX_NAME)
                        .id(String.valueOf(product.getCode()))
                        .source(JSONUtil.toJsonStr(product), XContentType.JSON);
                bulkRequest.add(indexRequest);
            }
            BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
            if (bulkResponse.hasFailures()) {
                throw new BizException(EsProductErrorCode.PRODUCT_DOCUMENT_BATCH_INSERT_ERROR);
            }
        } catch (IOException e) {
            log.error("批量插入产品文档异常 doc:{}", products, e);
            return false;
        }
        return true;
    }

    @Override
    public Boolean batchDeleteProductByProductCode(List<String> codeList) {
        log.info("批量删除产品文档-根据产品code doc:{}", codeList);
        // 删除请求
        DeleteByQueryRequest request = new DeleteByQueryRequest(INDEX_NAME);
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery()
                .must(QueryBuilders.termsQuery("code", codeList));
        request.setQuery(queryBuilder);
        // 调用客户端执行删除
        try {
            restHighLevelClient.deleteByQuery(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("批量删除产品文档异常", e);
            return false;
        }
        return true;
    }

    @Override
    public Boolean updateProductByProductCode(Product product) {
        log.info("更新产品文档-根据产品code doc:{}", product);
        // 更新文档请求
        UpdateRequest updateRequest = new UpdateRequest(INDEX_NAME, product.getCode());
        updateRequest.doc(JSONUtil.toJsonStr(product), XContentType.JSON);
        // 发送同步更新请求
        try {
            restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("更新文档异常：doc:{}", product, e);
            return false;
        }
        return true;
    }

    @Override
    public PageResult<Product> searchProductPage(QueryProductDTO dto) {
        log.info("分页查询产品文档 dto:{}", dto);
        // 1 构建SearchRequest请求对象，指定索引库
        SearchRequest searchRequest = new SearchRequest(INDEX_NAME);
        // 2 构造查询对象
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        // 设置查询超时时间 60 second
        searchSourceBuilder.timeout(EsBaseUtil.getTimeValue(60));
        // 设置分页
        EsBaseUtil.setPage(searchSourceBuilder, dto.getPage());
        // 设置排序
        if (!Objects.isNull(dto.getPrice_sort())) {
            SortOrder sortOrder = ProductPriceSortEnum.getSortOrder(dto.getPrice_sort());
            if (!Objects.isNull(sortOrder)) {
                searchSourceBuilder.sort("price", sortOrder);
            }
        }
        // 查询条件构建
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        // 产品ID集合查询
        if (CollectionUtil.isNotEmpty(dto.getId_list())) {
            queryBuilder.must(QueryBuilders.termsQuery("id", dto.getId_list()));
        }
        // 产品编号精准搜索
        if (StrUtil.isNotBlank(dto.getCode())) {
            queryBuilder.must(QueryBuilders.termQuery("code", dto.getCode()));
        }
        // 产品名称模糊查询
        if (StrUtil.isNotBlank(dto.getName())) {
            queryBuilder.must(QueryBuilders.wildcardQuery("name", "*" + dto.getName() + "*"));
        }
        // 产品价格范围查询
        if (dto.getStart_price() != null) {
            queryBuilder.must(QueryBuilders.rangeQuery("price").gte(dto.getStart_price()));
        }
        if (dto.getEnd_price() != null) {
            queryBuilder.must(QueryBuilders.rangeQuery("price").lte(dto.getEnd_price()));
        }
        // 或中至少有一个匹配 必须有个 or
//        queryBuilder.minimumShouldMatch(1);
        // 3 设置具体查询条件构建
        searchSourceBuilder.query(queryBuilder);
        // 4 将SearchSourceBuilder设置到SearchRequest中
        searchRequest.source(searchSourceBuilder);
        // 5 执行查询
        SearchResponse searchResponse = null;
        try {
            searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("分页查询产品文档异常", e);
        }
        // 6 处理查询对象分页对象返回
        return new EsBaseUtil<Product>().buildResultPage(searchResponse, Product.class, dto.getPage());

    }

    @Override
    public List<Product> searchProducts(String keyword, int page, int rows) {
        List<Product> arrayList = new ArrayList<>();
        Pageable pageable = PageRequest.of(page, rows); // 设置分页参数
        NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
                .withQuery(QueryBuilders.matchQuery("name", keyword).operator(Operator.AND)) // match查询
                .withPageable(pageable) //分页
                .withHighlightBuilder(getHighlightBuilder("name")) //高亮
                .withSort(new ScoreSortBuilder().order(SortOrder.DESC))
                .build();
        SearchHits<Product> searchHits = elasticsearchRestTemplate.search(nativeSearchQuery, Product.class);
        for (SearchHit<Product> searchHit : searchHits) { // 获取搜索到的数据
            Product content = searchHit.getContent();
            Product product = new Product();
            BeanUtils.copyProperties(content, product);

            //处理高亮
            Map<String, List<String>> highlightFields = searchHit.getHighlightFields();
            for (Map.Entry<String, List<String>> highlightFieldsStr : highlightFields.entrySet()) {
                String key = highlightFieldsStr.getKey();
                System.out.println(" key = " + key);
                if (StringUtils.equals(key, "name")) {
                    List<String> fragments = highlightFieldsStr.getValue();
                    StringBuilder sb = new StringBuilder();
                    for (String fragment : fragments) {
                        System.out.println(" -- " + fragment);
                        sb.append(fragment);
                    }
                    product.setName(sb.toString());
                }
            }
            arrayList.add(product);
        }
        return arrayList;
    }


    // 设置高亮字段
    public static HighlightBuilder getHighlightBuilder(String... fields) {
        // 高亮条件
        HighlightBuilder highlightBuilder = new HighlightBuilder(); // 生成高亮查询器
        for (String field : fields) {
            highlightBuilder.field(field);// 高亮查询字段
        }
        highlightBuilder.requireFieldMatch(false); // 如果要多个字段高亮,这项要为false
        highlightBuilder.preTags("<span style=\"color:red\">"); // 高亮设置
        highlightBuilder.postTags("</span>");
        // 下面这两项,如果你要高亮如文字内容等有很多字的字段,必须配置,不然会导致高亮不全,文章内容缺失等
        highlightBuilder.fragmentSize(800000); // 最大高亮分片数
        highlightBuilder.numOfFragments(0); // 从第一个分片获取高亮片段

        return highlightBuilder;
    }
}
